using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

using Android.App;
using Outcoder.Concurrency;

namespace Outcoder
{
	public partial class UISynchronizationContext : ISynchronizationContext
	{
		void EnsureInitialized()
		{
			
		}

		public void InvokeWithoutBlocking(SendOrPostCallback callback, object state,
			[CallerMemberName]string memberName = null,
			[CallerFilePath]string filePath = null,
			[CallerLineNumber]int lineNumber = 0)
		{
			ArgumentValidator.AssertNotNull(callback, "callback");
			EnsureInitialized();

			var invokableCallback = GetWrappedCallback(callback, state, memberName, filePath, lineNumber);
			Post(arg => invokableCallback(arg), state);
		}

		public void InvokeWithoutBlocking(Action action,
			[CallerMemberName]string memberName = null,
			[CallerFilePath]string filePath = null,
			[CallerLineNumber]int lineNumber = 0)
		{
			ArgumentValidator.AssertNotNull(action, "action");
			EnsureInitialized();

			Action invokableAction = GetWrappedAction(action, memberName, filePath, lineNumber);
			Post(state => invokableAction(), null);
		}

		public void InvokeAndBlockUntilCompletion(SendOrPostCallback callback, object state,
			[CallerMemberName]string memberName = null,
			[CallerFilePath]string filePath = null,
			[CallerLineNumber]int lineNumber = 0)
		{
			ArgumentValidator.AssertNotNull(callback, "callback");
			EnsureInitialized();

			var invokableCallback = GetWrappedCallback(callback, state, memberName, filePath, lineNumber);
			Send(arg => invokableCallback(arg), state);
		}

		public void InvokeAndBlockUntilCompletion(Action action,
			[CallerMemberName]string memberName = null,
			[CallerFilePath]string filePath = null,
			[CallerLineNumber]int lineNumber = 0)
		{
			ArgumentValidator.AssertNotNull(action, "action");
			EnsureInitialized();

			Action invokableAction = GetWrappedAction(action, memberName, filePath, lineNumber);
			Send(arg => invokableAction(), null);
		}

		public void InvokeIfRequired(Action action,
			[CallerMemberName]string memberName = null,
			[CallerFilePath]string filePath = null,
			[CallerLineNumber]int lineNumber = 0)
		{
			ArgumentValidator.AssertNotNull(action, "action");

			Action invokableAction = GetWrappedAction(action, memberName, filePath, lineNumber);

			if (Thread.CurrentThread.IsBackground)
			{
				Send(arg => invokableAction(), null);
			}
			else
			{
				invokableAction();
			}
		}

		public async Task InvokeIfRequiredAsync(
			Func<Task> action, [CallerMemberName]string memberName = null,
			[CallerFilePath]string filePath = null,
			[CallerLineNumber]int lineNumber = 0)
		{
			ArgumentValidator.AssertNotNull(action, "action");
			EnsureInitialized();

			Func<Task> invokableAction = GetWrappedAction(action, memberName, filePath, lineNumber);

			if (Thread.CurrentThread.IsBackground)
			{
				Send(async arg => await invokableAction(), null);
			}
			else
			{
				await invokableAction();
			}
		}

		void Send(Action<object> action, object state)
		{
			Application.SynchronizationContext.Send(s => action(s), state);
		}

		void Post(Action<object> action, object state)
		{
			Application.SynchronizationContext.Post(arg => action(arg), state);
		}

		public bool InvokeRequired
		{
			get
			{
				return Thread.CurrentThread.IsBackground;
			}
		}

		public void Initialize()
		{
			/* Intentionally left blank. */
		}

		#region InvokedExceptionHandler
		InvokedExceptionHandler invokedExceptionHandler;

		public InvokedExceptionHandler InvokedExceptionHandler
		{
			get
			{
				return invokedExceptionHandler;
			}
			set
			{
				invokedExceptionHandler = value;
			}
		}

		SendOrPostCallback GetWrappedCallback(SendOrPostCallback callback, object state,
			string memberName = null,
			string filePath = null,
			int lineNumber = 0)
		{
			if (invokedExceptionHandler == null)
			{
				return callback;
			}

			SendOrPostCallback wrappedCallback = delegate
			{
				try
				{
					callback.Invoke(state);
				}
				catch (Exception ex)
				{
					var args = new InvokedExceptionArgs(memberName, filePath, lineNumber, ex);
					bool handled = invokedExceptionHandler(this, args);

					if (!handled)
					{
						throw;
					}
				}
			};

			return wrappedCallback;
		}

		Action GetWrappedAction(Action action,
			string memberName = null,
			string filePath = null,
			int lineNumber = 0)
		{
			if (invokedExceptionHandler == null)
			{
				return action;
			}

			Action invokableAction = delegate
			{
				try
				{
					action();
				}
				catch (Exception ex)
				{
					var args = new InvokedExceptionArgs(memberName, filePath, lineNumber, ex);
					bool handled = invokedExceptionHandler(this, args);

					if (!handled)
					{
						throw;
					}
				}
			};

			return invokableAction;
		}

		Func<Task> GetWrappedAction(Func<Task> action,
			string memberName = null,
			string filePath = null,
			int lineNumber = 0)
		{
			if (invokedExceptionHandler == null)
			{
				return action;
			}

			Func<Task> invokableAction = async () =>
			{
				try
				{
					await action();
				}
				catch (Exception ex)
				{
					var args = new InvokedExceptionArgs(memberName, filePath, lineNumber, ex);
					bool handled = invokedExceptionHandler(this, args);

					if (!handled)
					{
						throw;
					}
				}
			};

			return invokableAction;
		}
		#endregion


		#region Singleton implementation

		static readonly UISynchronizationContext instance = new UISynchronizationContext();

		/// <summary>
		/// Gets the singleton instance.
		/// </summary>
		/// <value>The singleton instance.</value>
		public static UISynchronizationContext Instance
		{
			get
			{
				return instance;
			}
		}

		UISynchronizationContext()
		{
			/* Intentionally left blank. */
		}

		#endregion

	}
}